/**
 * 
 */
package xiaojian.toolkit.transport.codec;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.bean.bytebean.core.BeanFieldCodec;
import xiaojian.toolkit.bean.tlv.TLVSignal;
import xiaojian.toolkit.bean.tlv.decode.TLVDecoderOfBean;
import xiaojian.toolkit.bean.util.meta.Int2TypeMetainfo;
import xiaojian.toolkit.transport.protocol.xip.XipHeader;
import xiaojian.toolkit.util.ByteUtils;
import xiaojian.toolkit.util.MutableIdentifyable;



/**
 * @author isdom
 *
 */
public class MinaMixedDecoder extends CumulativeProtocolDecoder {

    private static final Logger logger = LoggerFactory.getLogger(MinaMixedDecoder.class);
    
    private final AttributeKey HEADER = new AttributeKey(getClass(), "XipHeader");

    private	BeanFieldCodec 		xipBeanCodec;
    private Int2TypeMetainfo 	xipTypeMetainfo;
    
    private	TLVDecoderOfBean 	tlvBeanDecoder;
    private	Int2TypeMetainfo	tlvTypeMetainfo;
    
    private	int					maxMessageLength = -1;
    
    private int dumpBytes = 256;
    private	boolean	isDebugEnabled;
    
	/* (non-Javadoc)
     * @see org.apache.mina.filter.codec.CumulativeProtocolDecoder#doDecode(org.apache.mina.core.session.IoSession, org.apache.mina.core.buffer.IoBuffer, org.apache.mina.filter.codec.ProtocolDecoderOutput)
     */
    @Override
    protected boolean doDecode(IoSession session, IoBuffer in,
            ProtocolDecoderOutput out) throws Exception {
        
        XipHeader header = (XipHeader)session.getAttribute(HEADER);
        if ( null == header ) {
        	int headerSize = xipBeanCodec.getStaticByteSize(XipHeader.class);
            if ( in.remaining() < headerSize ) {
                return false;
            }
            else {
                if ( logger.isDebugEnabled() && isDebugEnabled ) {
                    logger.debug("parse header... try parse...");
                }
                byte[] headerBytes = new byte[headerSize];
                in.get(headerBytes);
                
                if ( logger.isDebugEnabled() && isDebugEnabled) {
                    logger.debug("header raw bytes -->");
                    logger.debug( ByteUtils.bytesAsHexString(headerBytes, dumpBytes) );
                }
                
            	header = (XipHeader)xipBeanCodec.decode(
            			xipBeanCodec.getDecContextFactory().createDecContext(
            					headerBytes, XipHeader.class, null, null) ).getValue();
                if ( logger.isDebugEnabled() && isDebugEnabled) {
                    logger.debug("header-->" + header);
                }
                
                
                //	2011.06.23 fix bug: http://stcdev.sky-mobi.com/redmine/issues/3455
                if ( header.getMessageLength() < 0 ) {
            		logger.error( "header.messageLength (" + header.getMessageLength() 
            				+ ") < 0, so drop this connection.\r\ndump bytes received:\r\n" + 
            				ByteUtils.bytesAsHexString(headerBytes, dumpBytes) );
            		session.close(true);
            		return	false;
                }
                
                if ( maxMessageLength > 0 ) {
                	if ( header.getMessageLength() > maxMessageLength ) {
                		logger.error( "header.messageLength (" + header.getMessageLength() 
                				+ ") exceed maxMessageLength[" 
                				+ maxMessageLength + "], so drop this connection.\r\ndump bytes received:\r\n" + 
                				ByteUtils.bytesAsHexString(headerBytes, dumpBytes) );
                		session.close(true);
                		return	false;
                	}
                }
                	
                // Update the session attribute.
                setSessionXipHeader(session, header);
            }
        }
        
        if ( in.remaining() < header.getMessageLength() ) {
            return false;
        }
        else {
            //  为下一次在同一session上进行xip接受初始化环境
            removeSessionXipHeader(session);
            
            byte[] bytes = new byte[header.getMessageLength()];
            in.get(bytes);
            
            if ( logger.isDebugEnabled() && isDebugEnabled) {
                logger.debug("body raw bytes -->");
                logger.debug( ByteUtils.bytesAsHexString(bytes, dumpBytes) );
            }
            
            MutableIdentifyable identifyable = null;
            if ( 1 == header.getBasicVer() ) {
	            Class<?> type = xipTypeMetainfo.find(header.getMessageCode());
	            if ( null == type ) {
	            	throw new RuntimeException("unknow message code:" + header.getMessageCode());
	            }
	            identifyable = (MutableIdentifyable)xipBeanCodec.decode(
	        			xipBeanCodec.getDecContextFactory().createDecContext(
	        					bytes, type, null, null) ).getValue();
            }
            else if ( 2 == header.getBasicVer() ) {
                Class<?> type = tlvTypeMetainfo.find(header.getMessageCode());
	            if ( null == type ) {
	            	throw new RuntimeException("unknow tag:" + header.getMessageCode());
	            }
	            identifyable = (MutableIdentifyable)tlvBeanDecoder.decode(
	            		bytes.length, bytes, 
                		tlvBeanDecoder.getDecodeContextFactory().createDecodeContext(type, null));
            }
            else {
            	logger.error("invalid basic ver, while header is {}", header);
            	logger.error("raw body bytes is {}", 
            			ByteUtils.bytesAsHexString(bytes, header.getMessageLength()));
            	throw new RuntimeException("invalid basic ver {" + header.getBasicVer() + "}");
            }
            
            identifyable.setIdentification(header.getTransactionAsUUID());
            if ( identifyable instanceof TLVSignal ) {
            	((TLVSignal)identifyable).setSourceId(header.getSourceId());
            }
            
            if ( logger.isDebugEnabled() && isDebugEnabled) {
                logger.debug("signal-->" + identifyable);
            }
            
            out.write( identifyable );
            
            return true;
        }
    }

    @Override
    public void dispose(IoSession session) throws Exception {
        super.dispose(session);
        
        //  our dispose
        removeSessionXipHeader(session);
    }
    
    private void setSessionXipHeader(IoSession session, XipHeader header) {
        session.setAttribute(HEADER, header);
    }
    
    private void removeSessionXipHeader(IoSession session) {
        session.removeAttribute(HEADER);
    }

    /**
     * @param dumpBytes the dumpBytes to set
     */
    public void setDumpBytes(int dumpBytes) {
        this.dumpBytes = dumpBytes;
    }
    
	public int getDumpBytes() {
		return dumpBytes;
	}

	public boolean isDebugEnabled() {
		return isDebugEnabled;
	}

	public void setDebugEnabled(boolean isDebugEnabled) {
		this.isDebugEnabled = isDebugEnabled;
	}

	/**
	 * @return the tlvBeanDecoder
	 */
	public TLVDecoderOfBean getTlvBeanDecoder() {
		return tlvBeanDecoder;
	}

	/**
	 * @param tlvBeanDecoder the tlvBeanDecoder to set
	 */
	public void setTlvBeanDecoder(TLVDecoderOfBean tlvBeanDecoder) {
		this.tlvBeanDecoder = tlvBeanDecoder;
	}

	/**
	 * @return the xipBeanCodec
	 */
	public BeanFieldCodec getXipBeanCodec() {
		return xipBeanCodec;
	}

	/**
	 * @param xipBeanCodec the xipBeanCodec to set
	 */
	public void setXipBeanCodec(BeanFieldCodec xipBeanCodec) {
		this.xipBeanCodec = xipBeanCodec;
	}

	/**
	 * @return the maxMessageLength
	 */
	public int getMaxMessageLength() {
		return maxMessageLength;
	}

	/**
	 * @param maxMessageLength the maxMessageLength to set
	 */
	public void setMaxMessageLength(int maxMessageLength) {
		this.maxMessageLength = maxMessageLength;
	}

	/**
	 * @return the xipTypeMetainfo
	 */
	public Int2TypeMetainfo getXipTypeMetainfo() {
		return xipTypeMetainfo;
	}

	/**
	 * @param xipTypeMetainfo the xipTypeMetainfo to set
	 */
	public void setXipTypeMetainfo(Int2TypeMetainfo xipTypeMetainfo) {
		this.xipTypeMetainfo = xipTypeMetainfo;
	}

	/**
	 * @return the tlvTypeMetainfo
	 */
	public Int2TypeMetainfo getTlvTypeMetainfo() {
		return tlvTypeMetainfo;
	}

	/**
	 * @param tlvTypeMetainfo the tlvTypeMetainfo to set
	 */
	public void setTlvTypeMetainfo(Int2TypeMetainfo tlvTypeMetainfo) {
		this.tlvTypeMetainfo = tlvTypeMetainfo;
	}

}
